package org.web.base.helper;

import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;

public class BeanConvertHelper2 {

    private static final Logger logger = Logger.getLogger(BeanConvertHelper2.class);

    // 缓存类的setter方法
    private static final Map<Class<?>, Map<String, Method>> settersCache = new HashMap<>();
    private static final Map<Class<?>, Function<String, ?>> customConverters = new HashMap<>();
    private static final Map<String, String> fieldMappings = new HashMap<>();
    private static final String[] dateFormats = new String[]{
            "yyyy-MM-dd HH:mm:ss",
            "yyyy-MM-dd",
            "MM/dd/yyyy",
            "dd-MM-yyyy"
    };

    public static <T> T mapToBean(Map<String, Object> map, Class<T> clazz) {
        T obj;
        try {
            obj = clazz.getDeclaredConstructor().newInstance();
            Map<String, Method> setters = getSetters(clazz);
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String propertyName = entry.getKey();
                if (fieldMappings.containsKey(propertyName.toLowerCase())) {
                    propertyName = fieldMappings.get(propertyName.toLowerCase());
                }
                Object value = entry.getValue();

                Method setterMethod = setters.get(propertyName.toLowerCase());
                if (setterMethod != null) {
                    try {
                        Class<?> paramType = setterMethod.getParameterTypes()[0];
                        if (value != null) {
                            if (value.getClass() == String.class) {
                                value = convertType((String) value, paramType);
                            } else if (value instanceof Map && !Map.class.isAssignableFrom(paramType)) {
                                value = mapToBean((Map<String, Object>) value, paramType);
                            }
                        }
                        setterMethod.invoke(obj, value);
                    } catch (Exception e) {
                        logger.debug("Failed to set property '" + propertyName + "': " + e.getMessage());
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to map to bean: " + e.getMessage(), e);
        }
        return obj;
    }

    public static <T> Map<String, Object> beanToMap(T bean) {
        Map<String, Object> map = new HashMap<>();
        for (Method method : bean.getClass().getMethods()) {
            if (method.getName().startsWith("get") && method.getParameterCount() == 0) {
                String propertyName = method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4);
                try {
                    Object value = method.invoke(bean);
                    if (value instanceof Date) {
                        value = formatDate((Date) value);
                    }
                    map.put(propertyName, value);
                } catch (Exception e) {
                    logger.debug("Failed to get property '" + propertyName + "': " + e.getMessage());
                }
            }
        }
        return map;
    }

    public static <S, T> List<T> convertListToList(List<S> sourceList, Class<T> targetClass) {
        List<T> targetList = new ArrayList<>();
        for (S sourceObject : sourceList) {
            try {
                T targetObject = targetClass.getDeclaredConstructor().newInstance();
                if (sourceObject != null) {
                    BeanUtils.copyProperties(sourceObject, targetObject);
                } else if (targetObject instanceof Map) {
                    targetObject = mapToBean((Map<String, Object>) sourceObject, targetClass);
                }
                targetList.add(targetObject);
            } catch (Exception e) {
                throw new RuntimeException("Failed to convertListToList: " + e.getMessage(), e);
            }
        }
        return targetList;
    }

    private static String formatDate(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);

        boolean hasTime = calendar.get(Calendar.HOUR_OF_DAY) != 0
                || calendar.get(Calendar.MINUTE) != 0
                || calendar.get(Calendar.SECOND) != 0;

        String format = hasTime ? dateFormats[0] : dateFormats[1];
        return new SimpleDateFormat(format).format(date);
    }

    private static Map<String, Method> getSetters(Class<?> clazz) {
        // 检查缓存中是否已经存在
        if (settersCache.containsKey(clazz)) {
            return settersCache.get(clazz);
        }

        Map<String, Method> setters = new HashMap<>();
        for (Method method : clazz.getMethods()) {
            if (method.getName().startsWith("set") && method.getParameterCount() == 1) {
                String propertyName = method.getName().substring(3).toLowerCase();
                setters.put(propertyName, method);
            }
        }
        settersCache.put(clazz, setters);
        return setters;
    }


    private static Object convertType(String value, Class<?> targetType) {
        try {
            if (customConverters.containsKey(targetType)) {
                return customConverters.get(targetType).apply(value);
            }
            if (targetType == Integer.class || targetType == int.class) {
                return Integer.parseInt(value);
            } else if (targetType == Long.class || targetType == long.class) {
                return Long.parseLong(value);
            } else if (targetType == Double.class || targetType == double.class) {
                return Double.parseDouble(value);
            } else if (targetType == Float.class || targetType == float.class) {
                return Float.parseFloat(value);
            } else if (targetType == Boolean.class || targetType == boolean.class) {
                return Boolean.parseBoolean(value);
            } else if (targetType == Character.class || targetType == char.class) {
                return value.charAt(0);
            } else if (targetType == Date.class) {
                return parseDate(value);
            } else if (targetType == LocalDateTime.class) {
                return parseLocalDateTime(value);
            } else if (Collection.class.isAssignableFrom(targetType)) {
                return convertToCollection(value, targetType);
            } else if (Map.class.isAssignableFrom(targetType)) {
                // For nested Map
                return value;  // Return as is if the target type is a Map
            }
        } catch (Exception e) {
            logger.debug("Failed to convert value '" + value + "' to type '" + targetType + "': " + e.getMessage());
        }
        return value;  // 返回原始字符串，如果无法转换为目标类型
    }

    private static Date parseDate(String value) {
        for (String format : dateFormats) {
            try {
                return new SimpleDateFormat(format).parse(value);
            } catch (ParseException e) {
                // 继续尝试下一个格式
            }
        }
        logger.debug("Unparseable date: " + value);
        return null;
    }

    private static LocalDateTime parseLocalDateTime(String value) {
        for (String format : dateFormats) {
            try {
                DateTimeFormatter dtf = DateTimeFormatter.ofPattern(format);
                return LocalDateTime.parse(value, dtf);
            } catch (Exception e) {
                // 继续尝试下一个格式
            }
        }
        logger.debug("Unparseable date-time: " + value);
        return null;
    }

    private static Collection<?> convertToCollection(String value, Class<?> targetType) {
        // Assume value is a comma-separated list for simplicity
        String[] values = value.split(",");
        Collection<Object> collection;
        if (List.class.isAssignableFrom(targetType)) {
            collection = new ArrayList<>();
        } else if (Set.class.isAssignableFrom(targetType)) {
            collection = new HashSet<>();
        } else {
            throw new IllegalArgumentException("Unsupported collection type: " + targetType);
        }
        for (String val : values) {
            collection.add(val.trim()); // add each value after trimming
        }
        return collection;
    }

    public static void addFieldMapping(String mapFieldName, String beanFieldName) {
        fieldMappings.put(mapFieldName.toLowerCase(), beanFieldName.toLowerCase());
    }
}
